#! /usr/bin/python
# -*- coding: utf-8 -*-
# @author HanYZ
# @createAt 2022.08.31
# 网易萧先生-行情中继模型

import backtrader as bt

class StrategyClass(bt.Strategy):
    '''行情中继模型'''
    # {'validdays':10,'buyprice':0,'trailpercent':0.03,'planstop':1.1,'crossperiod': 3, 'bodylimit': 1.0}
    params = dict(
        mabodyperiod = 22,     # 柱体均值区间
        bodylimit = 1,         # 中柱界定，柱高与柱体均值的比值，0.95代表比均值略小也算中柱
        crossperiod = 3 ,      # 死叉金叉间隔
        planstop = 1.1,        # 计划止盈
        trailpercent = 0.03,   # 跟踪止损
        buyprice = 0,          # 大于0时以形态日收盘价的buyprice倍做限价买入，否则以市价买入
        validdays = 10,        # 限价单的有效期
        code = None,
        name = None,
        log = True,
        buyhook = None,
    )

    def log(self, txt, dt=None, force=False):
        if force or self.p.log:
            dt = (dt or self.data.datetime).date()
            who = f'{self.p.code} {self.p.name} ' if self.p.code else ''
            print(f'{dt.isoformat()} {who}{txt}')

    def notify_order(self, order):
        if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
            return

        if order.status == order.Completed:
            if order.isbuy():
                buytxt = '买入价:%.2f, 量:%s, 持仓:%s' % (order.executed.price, order.executed.size, self.position.size)
                if 'log' in order.info:
                    buytxt = '%s %s' % (buytxt, order.info.log)
                self.log(buytxt, bt.num2date(order.executed.dt))
            else:
                selltxt = '卖出价, %.2f, 量:%s, 持仓:%s' % (
                    order.executed.price, order.executed.size, self.position.size)
                if 'log' in order.info:
                    selltxt = '%s %s' % (selltxt, order.info.log)
                self.log(selltxt, bt.num2date(order.executed.dt))

        elif order.status in [order.Expired, order.Canceled, order.Margin]:
            if 'log' not in order.info:
                self.log('%s , %s' % (order.Status[order.status], order))
            pass

    def buyhook(self, text):
        if self.p.buyhook:
            self.p.buyhook(code=self.p.code, name=self.p.name, date=self.datetime.datetime(), shape='relaying', shape_cn='行情中继', log=text)
        else:
            self.log(text)

    def __init__(self):
        ma = lambda n: bt.ind.SMA(self.data, period=n)
        ma5, ma13, ma34 = ma(5), ma(13), ma(34)
        up = bt.And(ma34 < ma13, ma13 < ma5)
        red = self.data.close > self.data.open

        body = bt.If(red, self.data.close - self.data.open, self.data.open - self.data.close)
        mabody = bt.ind.SMA(body, period=self.p.mabodyperiod)

        cross = bt.ind.CrossOver(ma5, ma13)
        bigbody = body > mabody * self.p.bodylimit

        self.ma34 = ma34
        self.signalA = bt.ind.And(ma34 > ma34(-2), up(-1), cross < 0)
        self.signalB = bt.ind.And(
            ma34 > ma34(-2),
            bt.ind.Or(
                bt.ind.And(cross > 0, bigbody, red),
                bt.ind.And(cross(-1) > 0, bigbody, red),
                bt.ind.And(cross > 0, bigbody(-1), red(-1))
            )
        )

        # self.check = (cross, cross(-1), up, up(-1), bigbody, bigbody(-1), body, mabody)
        # self.checkma = (ma5, ma13, ma34, ma5(-1), ma13(-1), ma34(-1), ma34(-2))

        self.status = 0
        self.wait4crossup = -1

    def next(self):
        if self.status == 0:
            if self.signalA:
                self.log('多头死叉')
                self.cd_ma34 = self.ma34[0]
                self.wait4crossup = len(self) + self.p.crossperiod
                self.status = 1
        elif self.status == 1:
            if self.wait4crossup == len(self):
                self.log('未在短期内出现金叉，放弃观察')
                self.status = 0
            elif self.signalB:
                self.status = 0
                dday = (len(self) + self.p.crossperiod - self.wait4crossup)
                gradient = (self.ma34[0] * 100 / self.cd_ma34 - 100) / dday
                self.buyhook(f'行情中继，死叉:{self.cd_ma34}，金叉:{self.ma34[0]}，间隔:{dday}斜率:{gradient}')
                if self.p.buyprice > 0:
                    price = self.data.close[0] * self.p.buyprice
                    self.buy_bracket(
                        price=price,  oargs={"log": f"限价({price:.2f},{self.p.buyprice}%)买入, 斜率:{gradient}"},
                        stopexec=bt.Order.StopTrail, stopargs={"trailpercent": self.p.trailpercent, "log": f"跟踪止损 {self.p.trailpercent}"},
                        limitprice=price * self.p.planstop, limitargs={"log": "计划({self.p.planstop}%)止盈"}
                    )
                else:
                    self.buy_bracket(
                        exectype=bt.Order.Market, oargs={"log": f"市价买入, 斜率:{gradient}"},
                        stopexec=bt.Order.StopTrail, stopargs={"trailpercent": self.p.trailpercent, "log": f"跟踪止损 {self.p.trailpercent}"},
                        limitprice=self.data.close[0] * self.p.planstop, limitargs={"log": "计划({self.p.planstop}%)止盈"}
                    )

EasyNetRelaying = StrategyClass
